#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
设备模拟器 - 简洁版本
提供 Modbus RTU 从机服务，响应 Qt 程序的控制命令
"""

import asyncio
import logging
import struct
from datetime import datetime, timedelta
from enum import IntEnum
import random

from pymodbus.server import StartAsyncSerialServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import (
    ModbusSequentialDataBlock,
    ModbusSlaveContext,
    ModbusServerContext,
)

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class DeviceStatus(IntEnum):
    STATUS_RUNNING = 0
    STATUS_MAINTENANCE = 1
    STATUS_FAULT = 2
    STATUS_CALIB = 3
    STATUS_INSPECT = 4
    STATUS_ANALYSIS = 6
    STATUS_CALIBRATION = 7
    STATUS_IDLE = 8
    STATUS_OPERATION = 9
    STATUS_OTHER = 10


class DeviceCommand(IntEnum):
    CMD_ANALYSIS = 199
    CMD_CALIB_1 = 197
    CMD_CALIB_2 = 198
    CMD_CALIB_FULL = 355
    CMD_STD_CHECK = 208
    CMD_ZERO_CHECK = 356
    CMD_CLEAN = 207
    CMD_STOP = 209
    CMD_SET_TIME = 200


class DeviceSimulator:
    def __init__(self):
        # 设备状态
        self.current_status = DeviceStatus.STATUS_IDLE
        self.device_time = datetime.now()
        self.current_reading = 15.5

        # 校准数据
        self.std1_conc = 10.0
        self.std1_signal = 0.0
        self.std1_absorbance = 0.0
        self.std1_time = None

        self.std2_conc = 50.0
        self.std2_signal = 0.0
        self.std2_absorbance = 0.0
        self.std2_time = None

        self.check_conc = 25.0
        self.check_result = 0.0
        self.check_time = None

        # 命令处理
        self.processing_command = False
        self.command_task = None

        # 创建数据存储
        self.datastore = None
        self.setup_datastore()

    def setup_datastore(self):
        # 创建自定义的数据块来监控写入
        class CustomModbusSequentialDataBlock(ModbusSequentialDataBlock):
            def __init__(self, address, values, simulator=None):
                super().__init__(address, values)
                self.simulator = simulator

            def setValues(self, address, values):
                # 检查是否是外部写入（非模拟器自己的update_registers调用）
                import traceback

                stack = traceback.extract_stack()
                is_internal_update = any(
                    "update_registers" in frame.name for frame in stack
                )

                if (
                    not is_internal_update
                    and self.simulator
                    and hasattr(self.simulator, "handle_write")
                ):
                    self.simulator.handle_write(address - 1, values)
                return super().setValues(address, values)

        holding_registers = CustomModbusSequentialDataBlock(0, [0] * 1000, self)

        self.datastore = ModbusSlaveContext(
            di=ModbusSequentialDataBlock(0, [0] * 100),
            co=ModbusSequentialDataBlock(0, [0] * 100),
            hr=holding_registers,
            ir=ModbusSequentialDataBlock(0, [0] * 100),
        )

        # 初始化数据
        self.update_registers()

    def float_to_registers(self, value):
        """浮点数转寄存器(ABCD字节序)"""
        bytes_data = struct.pack(">f", value)
        high = struct.unpack(">H", bytes_data[0:2])[0]
        low = struct.unpack(">H", bytes_data[2:4])[0]
        return [high, low]

    def update_registers(self):
        # 当前测量值 (地址1-2, float)
        values = self.float_to_registers(self.current_reading)
        self.datastore.setValues(3, 1, values)

        # 设备状态 (地址18)
        self.datastore.setValues(3, 18, [int(self.current_status)])

        # 故障码 (地址19)
        self.datastore.setValues(3, 19, [0])

        # 设备时间 (地址201-206)
        time_values = [
            self.device_time.year,
            self.device_time.month,
            self.device_time.day,
            self.device_time.hour,
            self.device_time.minute,
            self.device_time.second,
        ]
        self.datastore.setValues(3, 201, time_values)

        # 校准数据
        self.datastore.setValues(
            3, 65, self.float_to_registers(self.std1_conc)
        )  # 标一浓度
        self.datastore.setValues(
            3, 67, self.float_to_registers(self.std1_signal)
        )  # 标一信号
        self.datastore.setValues(
            3, 69, self.float_to_registers(self.std2_conc)
        )  # 标二浓度
        self.datastore.setValues(
            3, 71, self.float_to_registers(self.std2_signal)
        )  # 标二信号
        self.datastore.setValues(
            3, 73, self.float_to_registers(self.check_conc)
        )  # 核查浓度
        self.datastore.setValues(
            3, 75, self.float_to_registers(self.check_result)
        )  # 核查结果
        self.datastore.setValues(
            3, 213, self.float_to_registers(self.std1_absorbance)
        )  # 标一吸光度
        self.datastore.setValues(
            3, 211, self.float_to_registers(self.std2_absorbance)
        )  # 标二吸光度

        # 校准时间
        if self.std1_time:
            std1_time_values = [
                self.std1_time.year,
                self.std1_time.month,
                self.std1_time.day,
                self.std1_time.hour,
                self.std1_time.minute,
                self.std1_time.second,
            ]
            self.datastore.setValues(3, 26, std1_time_values)

        if self.std2_time:
            std2_time_values = [
                self.std2_time.year,
                self.std2_time.month,
                self.std2_time.day,
                self.std2_time.hour,
                self.std2_time.minute,
                self.std2_time.second,
            ]
            self.datastore.setValues(3, 38, std2_time_values)

        if self.check_time:
            check_time_values = [
                self.check_time.year,
                self.check_time.month,
                self.check_time.day,
                self.check_time.hour,
                self.check_time.minute,
                self.check_time.second,
            ]
            self.datastore.setValues(3, 79, check_time_values)

    def handle_write(self, address, values):
        """处理写入请求"""
        logger.info(f"写入请求 - 地址: {address}, 值: {values}")

        # 处理命令
        if address in [cmd.value for cmd in DeviceCommand] and values[0] == 1:
            if not self.processing_command:
                asyncio.create_task(self.execute_command(DeviceCommand(address)))

        # 处理设置时间
        elif address == 200 and len(values) >= 7 and values[0] == 1:
            try:
                year, month, day, hour, minute, second = values[1:7]
                self.device_time = datetime(year, month, day, hour, minute, second)
                logger.info(f"时间已设置为: {self.device_time}")
            except:
                pass

    async def execute_command(self, command):
        """执行命令"""
        if self.processing_command:
            return

        self.processing_command = True
        logger.info(f"执行命令: {command.name}")

        try:
            if command == DeviceCommand.CMD_ANALYSIS:
                self.current_status = DeviceStatus.STATUS_ANALYSIS
                self.update_registers()
                await asyncio.sleep(10)  # 模拟10秒分析
                self.current_reading = 15.0 + random.random() * 20.0
                logger.info(f"分析完成: {self.current_reading:.2f}")

            elif command == DeviceCommand.CMD_CALIB_1:
                self.current_status = DeviceStatus.STATUS_CALIBRATION
                self.update_registers()
                await asyncio.sleep(20)  # 模拟20秒校准
                self.std1_time = datetime.now()
                self.std1_signal = self.std1_conc * 0.8 + (random.random() - 0.5) * 0.2
                self.std1_absorbance = (
                    self.std1_conc * 0.02 + (random.random() - 0.5) * 0.001
                )
                logger.info(f"标一校准完成")

            elif command == DeviceCommand.CMD_CALIB_2:
                self.current_status = DeviceStatus.STATUS_CALIBRATION
                self.update_registers()
                await asyncio.sleep(20)
                self.std2_time = datetime.now()
                self.std2_signal = self.std2_conc * 0.8 + (random.random() - 0.5) * 0.2
                self.std2_absorbance = (
                    self.std2_conc * 0.02 + (random.random() - 0.5) * 0.001
                )
                logger.info(f"标二校准完成")

            elif command == DeviceCommand.CMD_CALIB_FULL:
                self.current_status = DeviceStatus.STATUS_CALIBRATION
                self.update_registers()
                await asyncio.sleep(45)  # 完整校准45秒
                self.std1_time = self.std2_time = datetime.now()
                self.std1_signal = self.std1_conc * 0.8 + (random.random() - 0.5) * 0.2
                self.std1_absorbance = (
                    self.std1_conc * 0.02 + (random.random() - 0.5) * 0.001
                )
                self.std2_signal = self.std2_conc * 0.8 + (random.random() - 0.5) * 0.2
                self.std2_absorbance = (
                    self.std2_conc * 0.02 + (random.random() - 0.5) * 0.001
                )
                logger.info(f"完整校准完成")

            elif command == DeviceCommand.CMD_STD_CHECK:
                self.current_status = DeviceStatus.STATUS_INSPECT
                self.update_registers()
                await asyncio.sleep(15)
                self.check_time = datetime.now()
                self.check_result = (
                    self.check_conc * 0.95 + (random.random() - 0.5) * 0.1
                )
                logger.info(f"标液核查完成")

            elif command == DeviceCommand.CMD_CLEAN:
                self.current_status = DeviceStatus.STATUS_MAINTENANCE
                self.update_registers()
                await asyncio.sleep(12)
                logger.info(f"清洗完成")

            elif command == DeviceCommand.CMD_STOP:
                logger.info(f"设备停止")

        finally:
            self.current_status = DeviceStatus.STATUS_IDLE
            self.processing_command = False
            self.update_registers()

    async def update_loop(self):
        """定时更新"""
        while True:
            # 更新设备时间
            self.device_time += timedelta(seconds=1)

            # 空闲时轻微波动测量值
            if self.current_status == DeviceStatus.STATUS_IDLE:
                self.current_reading += (random.random() - 0.5) * 0.1
                self.current_reading = max(0, min(100, self.current_reading))

            self.update_registers()
            await asyncio.sleep(1)


# CustomModbusServerContext 被删除，因为pymodbus直接操作DataBlock层


async def main():
    import argparse

    parser = argparse.ArgumentParser(description="设备模拟器")
    parser.add_argument("--port", default="COM7", help="串口名称")
    parser.add_argument("--baudrate", type=int, default=9600, help="波特率")
    parser.add_argument("--slave-id", type=int, default=1, help="从机地址")

    args = parser.parse_args()

    # 创建模拟器实例
    simulator = DeviceSimulator()

    # 直接使用标准服务器上下文，因为逻辑已在DataBlock层处理
    context = ModbusServerContext(
        slaves={args.slave_id: simulator.datastore}, single=False
    )

    # 设备标识
    identity = ModbusDeviceIdentification()
    identity.VendorName = "StdStation"
    identity.ProductCode = "DeviceSimulator"
    identity.ProductName = "Python Device Simulator"
    identity.ModelName = "v1.0"

    logger.info(
        f"启动设备模拟器 - {args.port}:{args.baudrate}, 从机地址:{args.slave_id}"
    )

    # 启动更新循环
    update_task = asyncio.create_task(simulator.update_loop())

    try:
        # 启动 Modbus 服务器
        await StartAsyncSerialServer(
            context=context,
            identity=identity,
            port=args.port,
            baudrate=args.baudrate,
            bytesize=8,
            parity="N",
            stopbits=1,
            timeout=1,
        )
    except KeyboardInterrupt:
        logger.info("停止模拟器...")
        update_task.cancel()


if __name__ == "__main__":
    asyncio.run(main())
